<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="SIFye" id="SIFye"><span data-lake-id="ufff9d58b" id="ufff9d58b">典型回答</span></h1>
  <p data-lake-id="u5db1cec4" id="u5db1cec4"><br></p>
  <p data-lake-id="uef11cc74" id="uef11cc74"><span data-lake-id="ufffb349c" id="ufffb349c">（无关紧要的话：这个问题也困扰了我好久，之前一直没深究，毕竟这东西离开发太远了，但是最近在volatile的介绍中有很多人问这个东西到底是咋加的，还有的兄弟说我介绍的根本不对，那我就抽空研究了一下，和大家一起学习。但是本文的内容太底层了，我个人觉得了解即可。。。）</span></p>
  <p data-lake-id="ua0140094" id="ua0140094"><br></p>
  <p data-lake-id="u4058ba90" id="u4058ba90"><span data-lake-id="ub0b37a78" id="ub0b37a78">为了保证volatile变量的可见性和禁止指令重排序，Java会在生成的字节码中插入内存屏障来实现。</span></p>
  <p data-lake-id="u36ed95e1" id="u36ed95e1"><span data-lake-id="ueacce0a5" id="ueacce0a5">​</span><br></p>
  <p data-lake-id="uf4ec1fa2" id="uf4ec1fa2"><span data-lake-id="u4690b05c" id="u4690b05c">我们所说的内存屏障是一种CPU指令，它可以防止CPU及编译器对指令序列进行重排序，从而保证在代码执行过程中，对内存的读写操作按照程序员的意愿来进行。</span></p>
  <p data-lake-id="ub797b54a" id="ub797b54a"><span data-lake-id="u520a0f35" id="u520a0f35">​</span><br></p>
  <p data-lake-id="ud0854ba7" id="ud0854ba7"><span data-lake-id="u7a6ace46" id="u7a6ace46">关于内存屏障，推荐看一下，Doug Lea写的这篇文章：</span><a href="https://gee.cs.oswego.edu/dl/jmm/cookbook.html" target="_blank" data-lake-id="ua34e6504" id="ua34e6504"><span data-lake-id="u930759a5" id="u930759a5">https://gee.cs.oswego.edu/dl/jmm/cookbook.html</span></a></p>
  <p data-lake-id="u90ee5322" id="u90ee5322"><span data-lake-id="uf15a5874" id="uf15a5874">​</span><br></p>
  <h3 data-lake-id="K8XdK" id="K8XdK"><span data-lake-id="ud66329d2" id="ud66329d2">四种屏障</span></h3>
  <p data-lake-id="u83a848f5" id="u83a848f5"><br></p>
  <p data-lake-id="uf604fb9c" id="uf604fb9c"><span data-lake-id="uf6f6fb0b" id="uf6f6fb0b">volatile变量的内存屏障是通过一组指令来实现的，包括LoadLoad、LoadStore、StoreStore和StoreLoad。这些指令用于保证在volatile变量的读取和写入操作中，相邻指令之间的顺序不会被改变。</span></p>
  <p data-lake-id="ua81a7525" id="ua81a7525"><span data-lake-id="ub9ee0efb" id="ub9ee0efb">​</span><br></p>
  <ol list="u62f0568b">
   <li fid="u9d8815a4" data-lake-id="u057cb3b7" id="u057cb3b7"><strong><span data-lake-id="u826676aa" id="u826676aa">LoadLoad</span></strong><span data-lake-id="u96cb0158" id="u96cb0158">：</span><code data-lake-id="u09e409de" id="u09e409de"><span data-lake-id="u28a6adcc" id="u28a6adcc">Load1; LoadLoad; Load2</span></code><span data-lake-id="u97cb8a5c" id="u97cb8a5c">，确保 </span><span data-lake-id="ue46182a8" id="ue46182a8" style="background-color: #E7E9E8">Load1</span><span data-lake-id="u27bb6a58" id="u27bb6a58"> 加载数据先于 </span><span data-lake-id="uc4a4e143" id="uc4a4e143" style="background-color: #E7E9E8">Load2 和所有后续加载指令</span><span data-lake-id="u490787bf" id="u490787bf">。</span></li>
   <li fid="u9d8815a4" data-lake-id="u1ffc72c5" id="u1ffc72c5"><strong><span data-lake-id="uf929b277" id="uf929b277">LoadStore</span></strong><span data-lake-id="u44905500" id="u44905500">：</span><code data-lake-id="udf34dbba" id="udf34dbba"><span data-lake-id="ucaaa4b12" id="ucaaa4b12">Load1; LoadStore; Store2</span></code><span data-lake-id="uecd7a468" id="uecd7a468">，确保 </span><span data-lake-id="u36afb1f0" id="u36afb1f0" style="background-color: #E7E9E8">Load1</span><span data-lake-id="u3dae3ed4" id="u3dae3ed4"> 的数据加载先于 </span><span data-lake-id="u868fa8f0" id="u868fa8f0" style="background-color: #E7E9E8">Store2 及其后续存储指令将数据刷新到主内存</span><span data-lake-id="ueae6bdb5" id="ueae6bdb5">。</span></li>
   <li fid="u9d8815a4" data-lake-id="udc203ccd" id="udc203ccd"><strong><span data-lake-id="u8172802d" id="u8172802d">StoreStore</span></strong><span data-lake-id="uef383418" id="uef383418">：</span><code data-lake-id="uebfd64ad" id="uebfd64ad"><span data-lake-id="uf6861e79" id="uf6861e79" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">Load1; LoadStore; Store2</span></code><span data-lake-id="u598e86e3" id="u598e86e3">，确保 </span><span data-lake-id="udd07e633" id="udd07e633" style="background-color: #E7E9E8">Store1</span><span data-lake-id="uf3302dbe" id="uf3302dbe"> 的数据在</span><span data-lake-id="u91218d6d" id="u91218d6d" style="background-color: #E7E9E8">Store2 及所有后续存储指令相关的数据</span><span data-lake-id="ub302e303" id="ub302e303">之前对其他处理器可见（即刷新到内存中）</span></li>
   <li fid="u9d8815a4" data-lake-id="u1c9fd4b7" id="u1c9fd4b7"><strong><span data-lake-id="u62791b87" id="u62791b87">StoreLoad</span></strong><span data-lake-id="ud0e94175" id="ud0e94175">：</span><code data-lake-id="u42007615" id="u42007615"><span data-lake-id="ud475b0a5" id="ud475b0a5">Store1; StoreLoad; Load2</span></code><span data-lake-id="u09ab7912" id="u09ab7912">，确保 </span><span data-lake-id="uf88eda55" id="uf88eda55" style="background-color: #E7E9E8">Store1</span><span data-lake-id="u26b53d6b" id="u26b53d6b"> 的数据在 </span><span data-lake-id="uf483ab7d" id="uf483ab7d" style="background-color: #E7E9E8">Load2 及其后续加载指令访问的数据</span><span data-lake-id="u269c4d8c" id="u269c4d8c">之前对其他处理器可见（即刷新到主内存）</span></li>
  </ol>
  <p data-lake-id="u1c1e4d29" id="u1c1e4d29"><span data-lake-id="u256e1a7a" id="u256e1a7a">​</span><br></p>
  <blockquote data-lake-id="u5d749737" id="u5d749737">
   <p data-lake-id="ua1389e72" id="ua1389e72"><span data-lake-id="u4bceeef1" id="u4bceeef1">这里怎么记呢，就看操作顺序就行了，先load再load，中间加的就是LoadLoad，先Load再Store，中间加的就是LoadStore</span></p>
  </blockquote>
  <p data-lake-id="u6faa2a86" id="u6faa2a86"><span data-lake-id="u0b180094" id="u0b180094">​</span><br></p>
  <p data-lake-id="u57178963" id="u57178963"><span data-lake-id="u57bc5519" id="u57bc5519">但是，Doug Lea也说过，《很难找到一个” 最佳” 的位置使得最大限度地减少执行屏障的总数》，并且在不同的操作系统上，具体的添加屏障的情况也不一致，有些操作系统可以天然保证一些操作不会被重排序。</span></p>
  <p data-lake-id="ud07c4ef9" id="ud07c4ef9"><br></p>
  <p data-lake-id="u45d81058" id="u45d81058"><span data-lake-id="u435e3ad3" id="u435e3ad3">那么，如果严格一点的话，最完整的情况应该是这样加内存屏障，对于volatile变量来说：</span></p>
  <p data-lake-id="ue3a2515c" id="ue3a2515c"><span data-lake-id="u3b6bc740" id="u3b6bc740">​</span><br></p>
  <ul list="u00754d10">
   <li fid="uc39621e6" data-lake-id="u3c505398" id="u3c505398"><span data-lake-id="u946bb241" id="u946bb241">在每一个volatile的写(store)之前，加入一个StoreStore屏障和一个LoadStore屏障</span></li>
   <li fid="uc39621e6" data-lake-id="u62a4cfdb" id="u62a4cfdb"><span data-lake-id="u4f01b5a5" id="u4f01b5a5">在每一个volatile的写(store)之后，加入一个StoreLoad屏障和一个StroeStore屏障</span></li>
   <li fid="uc39621e6" data-lake-id="u8a34d822" id="u8a34d822"><span data-lake-id="u75c2b852" id="u75c2b852">在每一个volatile的读(load)之后，加一个LoadLoad屏障和LoadStrore屏障</span></li>
  </ul>
  <p data-lake-id="u7de87f6a" id="u7de87f6a"><span data-lake-id="ucb5483d3" id="ucb5483d3">​</span><br></p>
  <p data-lake-id="u4edd127c" id="u4edd127c"><span data-lake-id="ud917937d" id="ud917937d">即：</span></p>
  <p data-lake-id="u8234f0de" id="u8234f0de"><span data-lake-id="u031c3037" id="u031c3037">​</span><br></p>
  <pre lang="java"><code>
StoreStore
LoadStore

store

StoreLoad
StroeStore

-----


load

LoadLoad
LoadStrore
</code></pre>
  <p data-lake-id="ueae199af" id="ueae199af"><span data-lake-id="ueaaca78c" id="ueaaca78c" style="color: rgb(255, 0, 0); background-color: rgb(255, 255, 238)">​</span><br></p>
  <p data-lake-id="u5103eaca" id="u5103eaca"><span data-lake-id="u6b3b4dc2" id="u6b3b4dc2">以上定义可以在JDK的MemoryBarriers.java中找到：</span></p>
  <p data-lake-id="u01374855" id="u01374855"><span data-lake-id="u008ac162" id="u008ac162">​</span><br></p>
  <p data-lake-id="u2f756588" id="u2f756588"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703318784115-35593254-5b6f-485c-9613-41b9e67ff010.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_17%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u10086d6b" id="u10086d6b"><br></p>
  <p data-lake-id="u1da9c368" id="u1da9c368"><span data-lake-id="u8bf87df6" id="u8bf87df6">但是这只是最严格的情况，在不同的操作系统上面，具体实现方式也不尽相同，这几个内存屏障被定义在</span><a href="https://github.com/openjdk/jdk/blob/28c82bf18d85be00bea45daf81c6a9d665ac676f/src/hotspot/share/runtime/orderAccess.hpp" target="_blank" data-lake-id="u1a84ef98" id="u1a84ef98"><span data-lake-id="uc98efa5b" id="uc98efa5b">orderAccess.hpp</span></a><span data-lake-id="ub1a837c3" id="ub1a837c3">中。而且在实际实现时，肯定是能尽可能少的加屏障最好了。</span></p>
  <p data-lake-id="u4f6a1a06" id="u4f6a1a06"><span data-lake-id="u03b01e11" id="u03b01e11">​</span><br></p>
  <pre lang="java"><code>
class OrderAccess : public AllStatic {
 public:
  // barriers
  static void     loadload();
  static void     storestore();
  static void     loadstore();
  static void     storeload();

  static void     acquire();
  static void     release();
  static void     fence();
}
</code></pre>
  <p data-lake-id="u52905c50" id="u52905c50"><span data-lake-id="u066134da" id="u066134da">​</span><br></p>
  <p data-lake-id="u6c182d89" id="u6c182d89"><span data-lake-id="uaf3bd95b" id="uaf3bd95b">不同操作系统有自己不同的实现：</span></p>
  <p data-lake-id="u08395ddf" id="u08395ddf"><span data-lake-id="ub94ea8b1" id="ub94ea8b1">​</span><br></p>
  <p data-lake-id="u5fdaecc2" id="u5fdaecc2"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703319789644-62b6375c-1b46-4d32-b014-b70fc2cf8cf7.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u027629e3" id="u027629e3"><br></p>
  <p data-lake-id="ua6fccc00" id="ua6fccc00"><br></p>
  <blockquote data-lake-id="u538e9a04" id="u538e9a04">
   <p data-lake-id="u823b4e77" id="u823b4e77"><span data-lake-id="u8d7bae7c" id="u8d7bae7c">no-op表示不需要任何操作，操作系统已经天然支持的内存屏障保障。</span></p>
   <p data-lake-id="u3548ad3e" id="u3548ad3e"><span data-lake-id="uf5f8436a" id="uf5f8436a">acq，表示acquire，表示内存屏障只不允许其后面的读写向前越过屏障，挡后不挡前;</span></p>
   <p data-lake-id="u74cb1f7d" id="u74cb1f7d"><span data-lake-id="u75f2a4f6" id="u75f2a4f6">rel，表示release，表示内存屏障只不允许其前面的读写向后越过屏障，挡前不挡后；</span></p>
   <p data-lake-id="u429afef4" id="u429afef4"><span data-lake-id="u5c1ba801" id="u5c1ba801">stlr 的全称是 store release register，包括 StoreStore barrier 和 LoadStore barrier（场景少），通常使用 release 语义将寄存器的值写入内存；</span></p>
   <p data-lake-id="uf7439a3e" id="uf7439a3e"><span data-lake-id="ue892ba7d" id="ue892ba7d">ldar 的全称是 load acquire register，包括 LoadLoad barrier 和 LoadStore barrier（对，你没看错，我没写错），通常使用 acquire 语义从内存中将值加载入寄存器；</span></p>
   <p data-lake-id="u08b546af" id="u08b546af"><span data-lake-id="u6a3aee22" id="u6a3aee22">​</span><br></p>
   <p data-lake-id="ud778cfd3" id="ud778cfd3"><span data-lake-id="ufc4aa3dc" id="ufc4aa3dc">（这个我觉得不需要被，也不太用关注，只不过注意一下这里的release和acquire等操作就行了，因为看JDK源码的时候，会有响应的代码）</span></p>
   <p data-lake-id="ubed8be44" id="ubed8be44"><span data-lake-id="uaa39127b" id="uaa39127b">所以，只需要记住：</span></p>
   <p data-lake-id="u2d602a63" id="u2d602a63"><span data-lake-id="ub94d900b" id="ub94d900b">​</span><br></p>
   <p data-lake-id="u4991b27c" id="u4991b27c"><span data-lake-id="u5d7d5b44" id="u5d7d5b44" style="color: #DF2A3F">acquire 相当于在 Load 后面加上 LoadLoad，LoadStore </span></p>
   <p data-lake-id="uf1e254f8" id="uf1e254f8"><span data-lake-id="u3f259918" id="u3f259918" style="color: #DF2A3F">release 其实相当于在 Store 前面加上 LoadStore 和 StoreStore</span></p>
  </blockquote>
  <p data-lake-id="ubabf1410" id="ubabf1410"><span data-lake-id="u567f02e9" id="u567f02e9">​</span><br></p>
  <p data-lake-id="uc4d103fd" id="uc4d103fd"><span data-lake-id="u12f50471" id="u12f50471">那对应到代码实现上，可以在JDK中找到很多具体实现。</span></p>
  <p data-lake-id="ub9587b7f" id="ub9587b7f"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703319300708-acd6a0d7-304d-4b00-9b6a-5093d33bfff7.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_21%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <h3 data-lake-id="DFkYL" id="DFkYL"><span data-lake-id="u611c2309" id="u611c2309">X86实现</span></h3>
  <p data-lake-id="ub330c564" id="ub330c564"><br></p>
  <p data-lake-id="u77c1d58b" id="u77c1d58b"><span data-lake-id="u76faf43e" id="u76faf43e">再贴一下图，免得往前翻了，重点看X86这行</span></p>
  <p data-lake-id="u30ae6b6a" id="u30ae6b6a"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703321140976-f43cb4f1-ddcd-4d7d-8689-d4148fa47087.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u0afad4a6" id="u0afad4a6"><br></p>
  <p data-lake-id="u9af91c40" id="u9af91c40"><span data-lake-id="uf0070407" id="uf0070407">比如我们看下X86的实现：orderAccess_windows_x86.hpp、orderAccess_linux_x86.hpp等中这几个方法的实现：</span></p>
  <p data-lake-id="u94997cc1" id="u94997cc1"><span data-lake-id="u7e302ac0" id="u7e302ac0">​</span><br></p>
  <pre lang="java"><code>
inline void OrderAccess::loadload()   { compiler_barrier(); }
inline void OrderAccess::storestore() { compiler_barrier(); }
inline void OrderAccess::loadstore()  { compiler_barrier(); }
inline void OrderAccess::storeload()  { fence(); }

inline void OrderAccess::acquire()    { compiler_barrier(); }
inline void OrderAccess::release()    { compiler_barrier(); }
</code></pre>
  <p data-lake-id="u85fb1dfd" id="u85fb1dfd"><br></p>
  <p data-lake-id="u4e6a608f" id="u4e6a608f"><span data-lake-id="ubda5c54e" id="ubda5c54e">可以看到，针对X86天然支持的loadload、storestore、loadstore这里没有针对CPU做什么特殊的操作，只是调用编译器屏障的代码了。</span></p>
  <p data-lake-id="u179a1983" id="u179a1983"><span data-lake-id="ua69cfb2c" id="ua69cfb2c">​</span><br></p>
  <p data-lake-id="u26da0276" id="u26da0276"><span data-lake-id="u75a1dce9" id="u75a1dce9">而对于需要处理的storeload，这里单独做了一个实现（以下是orderAccess_windows_x86的实现）。</span></p>
  <p data-lake-id="uc049ae36" id="uc049ae36"><br></p>
  <pre lang="java"><code>
inline void OrderAccess::fence() {
#ifdef AMD64
  StubRoutines_fence();
#else
  __asm {
    lock add dword ptr [esp], 0;
  }
#endif // AMD64
  compiler_barrier();
}
</code></pre>
  <p data-lake-id="u368c48f4" id="u368c48f4"><span data-lake-id="u07873b41" id="u07873b41">​</span><br></p>
  <h3 data-lake-id="XuDqc" id="XuDqc"><span data-lake-id="u95ab04c5" id="u95ab04c5">ARM实现</span></h3>
  <p data-lake-id="u84c71588" id="u84c71588"><br></p>
  <p data-lake-id="u9cdfc0ad" id="u9cdfc0ad"><span data-lake-id="u79db0081" id="u79db0081">再贴一下图，免得往前翻了，重点看ARM这行：</span></p>
  <p data-lake-id="u4c8fc540" id="u4c8fc540"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1703321140976-f43cb4f1-ddcd-4d7d-8689-d4148fa47087.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u28e64786" id="u28e64786"><br></p>
  <p data-lake-id="ueea1dd07" id="ueea1dd07"><br></p>
  <p data-lake-id="u77566fde" id="u77566fde"><span data-lake-id="u72d2b58c" id="u72d2b58c">然后再对照着代码：orderAccess_linux_arm.hpp</span></p>
  <p data-lake-id="u4ad04e19" id="u4ad04e19"><span data-lake-id="ubd68bc2b" id="ubd68bc2b">​</span><br></p>
  <pre lang="java"><code>
inline void OrderAccess::loadload()   { dmb_ld(); }
inline void OrderAccess::loadstore()  { dmb_ld(); }
inline void OrderAccess::acquire()    { dmb_ld(); }
inline void OrderAccess::storestore() { dmb_st(); }
inline void OrderAccess::storeload()  { dmb_sy(); }
inline void OrderAccess::release()    { dmb_sy(); }
inline void OrderAccess::fence()      { dmb_sy(); }
</code></pre>
  <p data-lake-id="u85f80665" id="u85f80665"><span data-lake-id="u5dc4f19e" id="u5dc4f19e">​</span><br></p>
  <p data-lake-id="u4fd6040a" id="u4fd6040a"><span data-lake-id="u2c2b04d4" id="u2c2b04d4">可以看到不同的方法是采用不同的方式实现的。</span></p>
  <p data-lake-id="u47c44838" id="u47c44838"><span data-lake-id="u836e9da2" id="u836e9da2">​</span><br></p>
  <h3 data-lake-id="d4lxQ" id="d4lxQ"><span data-lake-id="u77c258a7" id="u77c258a7">总结</span></h3>
  <p data-lake-id="ubb8170ce" id="ubb8170ce"><br></p>
  <p data-lake-id="u568751bb" id="u568751bb"><span data-lake-id="uec44797e" id="uec44797e">内存屏障，主要是为了防止CPU及编译器的指令重排的，因为不同的CPU对于内存屏障的支持和实现都不一样，所以在JDK中，虽然定义了四种屏障，但是在具体加屏障过程中可能并不完全一样。</span></p>
  <p data-lake-id="uc25bc970" id="uc25bc970"><span data-lake-id="u055b7efe" id="u055b7efe">​</span><br></p>
  <p data-lake-id="u2f0629f4" id="u2f0629f4"><span data-lake-id="u1bab9fec" id="u1bab9fec">如果严格来说，那么保证一定不出问题，那么就是在volatile变量的，store前面添加StoreStore、LoadStore；在store后面添加StoreLoad和StoreStore，在load后面添加LoadLoad、LoadStore。</span></p>
  <p data-lake-id="u6482eb7f" id="u6482eb7f"><span data-lake-id="ua5a8f8d3" id="ua5a8f8d3">​</span><br></p>
  <p data-lake-id="u3646286d" id="u3646286d"><span data-lake-id="ub201ea0f" id="ub201ea0f">但是，加太多屏障也会影响性能，因为指令重排也是一个重要的性能优化手段，所以，在实际实现中可能会做一些优化，减少屏障数量，比如X86中，在操作系统上，就只需要针对StoreLoad加屏障。</span></p>
  <p data-lake-id="u753dcbd7" id="u753dcbd7"><span data-lake-id="u85a3d16a" id="u85a3d16a">​</span><br></p>
 </body>
</html>